// TODO: de-anonymize filter functions so they can use this.XYZ variables
function AccountDb(fdbid, bdbid, tbid) {             // Front dbid vs. Backend dbid
  // The database that makes up an account
  this.tbid   = tbid;
  this.snpPtr = snp.getPtr(fdbid, tbid);
  this.txnPtr = txn.getPtr(bdbid);
  this.hldPtr = hld.getPtr(bdbid);
  this.stmPtr = stm.getPtr(bdbid);
  this.yrsPtr = yrs.getPtr(bdbid);
  this.prtPtr = prt.getPtr(bdbid);
  this.incPtr = inc.getPtr(bdbid);
}
AccountDb.prototype = {
  aggregate: function() {
    Logger.log("AccountDb.aggregate()");
    var pyid = yrs.lookupPrevYr(this.yrsPtr, yid);   // TODO: what if empty? 
    var sid  = yrs.lookupLastSt(this.yrsPtr, yid);   // TODO: what if empty? Do we really need this!?
    var updt = prt.getPortfolio(this.prtPtr, pyid);  // TODO: what if empty?
    for (var hid in updt) {    // Reset returns calculations fields for this year/period
      updt[hid][prt.inc] = 0;updt[hid][prt.cfl] = 0;updt[hid][prt.wcf] = 0;updt[hid][prt.rtn] = 0;
    }
    var trans = txn.getReconciledYr(this.txnPtr, yid);
    if (trans.length > 0) {    // No transactions on this statement
      var yrEnd = yrs.lookupEndTime(this.yrsPtr, yid);
      var yrLen = yrEnd - yrs.lookupStrTime(this.yrsPtr, yid);
      var hid   = "";
      for (var i=0; i<trans.length; i++) {
        hid = trans[i][txn.hid];
        if (!updt.hasOwnProperty(hid))
          updt[hid] = [yid, hid, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        switch (trans[i][txn.txn]) {
          case "Distribution": // Add up accrued income
            updt[hid]    [prt.tin] += trans[i][txn.ttl]; 
            updt[hid]    [prt.inc] += trans[i][txn.ttl]; 
            updt[act.ttl][prt.tin] += trans[i][txn.ttl];
            updt[act.ttl][prt.inc] += trans[i][txn.ttl];
            break;;
          case "Add":          // Accumulate qty and bkv
          case "Buy":
          case "Reinvest":
            updt[hid][prt.qty] +=  trans[i][txn.qty];
            updt[hid][prt.bkv] +=  trans[i][txn.ttl];
            updt[hid][prt.avc]  =  divBy(updt[hid][prt.bkv], updt[hid][prt.qty]);
            updt[hid][prt.ttc] +=  trans[i][txn.ttl];
            updt[hid][prt.cfl] +=  trans[i][txn.ttl];
            updt[hid][prt.wcf] += (trans[i][txn.ttl] * (yrEnd - trans[i][txn.dte].getTime())/yrLen);
            updt[act.ttl]          [prt.cfl] +=  trans[i][txn.ttl];
            updt[act.ttl]          [prt.wcf] += (trans[i][txn.ttl] * (yrEnd - trans[i][txn.dte].getTime())/yrLen);
            break;;
          case "Remove":       // Remove qty and bkv
          case "Sell":
            updt[hid][prt.qty] -=  trans[i][txn.qty];
            updt[hid][prt.bkv]  =  updt[hid][prt.qty] * updt[hid][prt.avc];
            updt[hid][prt.rlz] +=  trans[i][txn.qty] * updt[hid][prt.avc];
            updt[hid][act.cfl] -=  trans[i][txn.ttl];
            updt[hid][act.wcf] -= (trans[i][txn.ttl] * (yrEnd - trans[i][txn.dte].getTime())/yrLen);
            updt[act.ttl]          [act.cfl] -=  trans[i][txn.ttl];
            updt[act.ttl]          [act.wcf] -= (trans[i][txn.ttl] * (yrEnd - trans[i][txn.dte].getTime())/yrLen);
            break;;
        }    
      }
      var prcs = act.getCurrent(this.actPtr); // TODO: what if empty? Shouldn't happen?
      // Calculate the Period's returns
      var pyid = yrs.lookupPrevYr(this.yrsPtr, yid);
      var prev = pyid !== null && pyid !== "" ? prt.getPortfolio(this.prtPtr, pyid) : null;
      var hbmv = 0; // The holding's previous period's ending market value is this period's Beginning Market Value
      for (var hid in updt) {
        if (hid !== act.csh) { // TODO: calculate return on cash (contributions) to the account 
          hbmv = prev != null && prev.hasOwnProperty(hid) ? prev[hid][prt.mkv] : 0; 
          // TODO: put divBy in Utilities file; put modifiedDietz in a function
          updt[hid][prt.rtn] = divBy((updt[hid][prt.mkv]+updt[hid][prt.inc]-hbmv-updt[hid][prt.cfl]),
                                     (hbmv+updt[hid][prt.wcf]));
          // Catch cases where Modified Dietz doesn't work! Use Simple Dietz instead
          if (updt[hid][prt.rtn]<=-1 && updt[hid][prt.mkv]>0) {
            updt[hid][prt.wcf] = updt[hid][prt.cfl] / 2; // Weight the cash flow all at the middle of the period
            updt[hid][prt.rtn] = divBy((updt[hid][prt.mkv]+updt[hid][prt.inc]-hbmv-updt[hid][prt.cfl]),
                                       (hbmv+updt[hid][prt.wcf]));
          }
        }
      }
    }
    prt.putPortfolio(this.prtPtr, updt);
  }
};
